/**
 * Copyright 2007 Jason Horman, Pete Aykroyd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.functor;

import org.functor.expression.And;
import org.functor.expression.Junction;
import org.functor.expression.Not;
import org.functor.expression.Or;
import org.functor.expression.*;

public class BooleanAlgorithms {
			/**
		 * Determines whether two boolean expressions conflict. For this to work, the algorithm makes the
		 * assumption that given BooleanFunctor a, b, a and b conflict <br/>
		 * <code> if a.class == b.class && a != b </code> <br/>
		 *
		 * @param p1
		 * @param p2
		 * @return true if p1 conflicts with p2
		 */
		public static boolean conflicts(PredicateExpression p1, PredicateExpression p2) {
			if (p1 == null || p2 == null)
				throw new IllegalArgumentException("Arguments to conflict can't be null.");

			// if it's an AND, then if there conflicts with any of its children, there is a conflict
			if (p1 instanceof org.functor.expression.And) {
				for (Object child : ((And) p1).getPredicates())
					if (conflicts((PredicateExpression) child, p2))
						return true;
				return false;
			}
			// for an OR, if p2 doesn't conflict with one of p1's predicates, then there is no conflict
			else if (p1 instanceof Or) {
				for (Object child : ((Or) p1).getPredicates())
					if (!conflicts((PredicateExpression) child, p2))
						return false;
				return true;
			}
			// return the Not of the conflict with not's predicate
			else if (p1 instanceof Not) {
				return conflictsForNots(((Not) p1).getPredicate(), p2);
			}
			// now we have a base predicate for p1, we need the same for p2
			else if (p2 instanceof And) {
				for (Object child : ((And) p2).getPredicates())
					if (conflicts(p1, (PredicateExpression) child))
						return true;
				return false;
			} else if (p2 instanceof Or) {
				for (Object child : ((Or) p2).getPredicates())
					if (!conflicts(p1, (PredicateExpression) child))
						return false;
				return true;
			} else if (p2 instanceof Not) {
				return conflictsForNots(p1, ((Not) p2).getPredicate());
			} else if (p1 instanceof Conflicts && p2 instanceof Conflicts) {
				return ((Conflicts) p1).conflicts(p2);
			}
			// now that we have base predicates - if they are of the same class, but not equal, there is a conflict
			else {
				return p1.getClass().equals(p2.getClass()) && !p1.equals(p2);
			}
		}

		/** only used for determining conflicts predicates of NOTs */
		private static boolean conflictsForNots(PredicateExpression p1, PredicateExpression p2) {
			if (p1 == null || p2 == null)
				throw new IllegalArgumentException("Arguments to conflict can't be null.");

			// if it's an AND, all the children must match
			if (p1 instanceof Junction) {
				for (Object child : ((Junction) p1).getPredicates())
					if (conflictsForNots((PredicateExpression) child, p2))
						return true;
				return false;
			}
			// return the Not of the match of its child
			else if (p1 instanceof Not) {
				return conflictsForNots(((Not) p1).getPredicate(), p2);
			}
			// now we have a base predicate for p1, we need the same for p2
			else if (p2 instanceof Junction) {
				for (Object child : ((Junction) p2).getPredicates())
					if (conflictsForNots(p1, (PredicateExpression) child))
						return true;
				return false;
			} else if (p2 instanceof Not) {
				return false;
			}
			// now that we have base predicates - if they are equal then they match
			else {
				return p1.equals(p2);
			}
		}

		public static UnaryPredicate<PredicateExpression> conflicts(PredicateExpression expression) {
			return conflicts.bindLeft(expression);
		}

		private static BinaryPredicate<PredicateExpression, PredicateExpression> conflicts = new BinaryPredicate<PredicateExpression, PredicateExpression>() {
			public boolean test(PredicateExpression left, PredicateExpression right) {
				return conflicts(left, right);
			}
		};
}
